﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using UnityEngine.Video;

namespace ResLibary
{
    public struct ABStructObj
    {
        public string bName;
        public string bundleUrl;
        public string[] dependencies;
        public AssetStructObj[] contents;

        public ABStructObj(string abName, string bpath, string[] dependes, AssetStructObj[] assets)
        {
            bName = abName;
            bundleUrl = bpath;
            contents = assets;
            dependencies = dependes;
        }
    }
    public struct AssetStructObj
    {
        public string assetName;
        public string assetType;
        public AssetStructObj(string aname, string sType)
        {
            assetName = aname;
            assetType = sType;
        }
    }
    public class AssetBundleLibary : ILibaryHandle
    {
        private class ABContentAssetObj
        {
            public string assetType;
            public string assetName;
            public string bundelName;
        }
        private class AssetBundleObj
        {
            public string bName;
            public string bundlePath;
            public AssetBundle bundle;
            public string[] dependencies;
            public List<string> allDependencies;
            //public Dictionary<string, List<string>> contentDic;
            public Dictionary<AssetExistStatusEnum, int> quoteDic;
            public AssetExistStatusEnum bundelExistStatus = AssetExistStatusEnum.Once;

            public AssetBundleObj()
            {
                allDependencies = new List<string>();
                //contentDic = new Dictionary<string, List<string>>();
                quoteDic = new Dictionary<AssetExistStatusEnum, int>();
                quoteDic[AssetExistStatusEnum.Globle] = 0;
                quoteDic[AssetExistStatusEnum.Once] = 0;
                quoteDic[AssetExistStatusEnum.Quote] = 0;
                quoteDic[AssetExistStatusEnum.Scene] = 0;
            }
        }

        private static AssetBundleLibary play_singleton = default(AssetBundleLibary);
#if UNITY_EDITOR
        private static AssetBundleLibary edit_singleton = default(AssetBundleLibary);
#endif
        private static object s_objectLock = new object();
        public static AssetBundleLibary Instance
        {
            get
            {
#if UNITY_EDITOR
                if (!ResLibaryConfig.isPlaying)
                {
                    if (AssetBundleLibary.play_singleton != null)
                    {
                        AssetBundleLibary.play_singleton = null;
                    }
                    if (AssetBundleLibary.edit_singleton == null)
                    {
                        object obj;
                        Monitor.Enter(obj = AssetBundleLibary.s_objectLock);//加锁防止多线程创建单例
                        try
                        {
                            if (AssetBundleLibary.edit_singleton == null)
                            {
                                AssetBundleLibary.edit_singleton = ((default(AssetBundleLibary) == null) ? Activator.CreateInstance<AssetBundleLibary>() : default(AssetBundleLibary));//创建单例的实例
                            }
                        }
                        finally
                        {
                            Monitor.Exit(obj);
                        }
                    }
                    return AssetBundleLibary.edit_singleton;
                }
                else
                {
                    if (AssetBundleLibary.edit_singleton != null)
                    {
                        AssetBundleLibary.edit_singleton = null;
                    }
                    if (AssetBundleLibary.play_singleton == null)
                    {
                        object obj;
                        Monitor.Enter(obj = AssetBundleLibary.s_objectLock);//加锁防止多线程创建单例
                        try
                        {
                            if (AssetBundleLibary.play_singleton == null)
                            {
                                AssetBundleLibary.play_singleton = ((default(AssetBundleLibary) == null) ? Activator.CreateInstance<AssetBundleLibary>() : default(AssetBundleLibary));//创建单例的实例
                            }
                        }
                        finally
                        {
                            Monitor.Exit(obj);
                        }
                    }
                    return AssetBundleLibary.play_singleton;
                }
#else
                if (AssetBundleLibary.play_singleton == null)
                {
                    object obj;
                    Monitor.Enter(obj = AssetBundleLibary.s_objectLock);//加锁防止多线程创建单例
                    try
                    {
                        if (AssetBundleLibary.play_singleton == null)
                        {
                            AssetBundleLibary.play_singleton = ((default(AssetBundleLibary) == null) ? Activator.CreateInstance<AssetBundleLibary>() : default(AssetBundleLibary));//创建单例的实例
                        }
                    }
                    finally
                    {
                        Monitor.Exit(obj);
                    }
                }
                return AssetBundleLibary.play_singleton;
#endif
            }
        }

        public static System.Action<LibaryStateObj> UpdateAssetCallback;
        private Dictionary<string, AssetBundleObj> bundleDic;
        private Dictionary<string, Dictionary<string, ABContentAssetObj>> resourceDict;
        private bool _hasLoadAsset = true;
        bool ILibaryHandle.HasLoadAsset { get { return _hasLoadAsset; } }
        public AssetBundleLibary()
        {
            bundleDic = new Dictionary<string, AssetBundleObj>();
            resourceDict = new Dictionary<string, Dictionary<string, ABContentAssetObj>>();
        }

        /// <summary>
        /// 异步加载
        /// </summary>
        /// <param name="assetType"></param>
        /// <param name="assetName"></param>
        /// <param name="callback"></param>
        public void LoadAssetAsyn(string assetType, string assetName, System.Action<string> callback)
        {
            if (!resourceDict.ContainsKey(assetType))
            {
                if (callback != null)
                    callback("not eixst");
                return;
            }
            if (!resourceDict[assetType].ContainsKey(assetName))
            {
                if (callback != null)
                    callback("not eixst");
                return;
            }
            LoadBundleAsyn(resourceDict[assetType][assetName].bundelName, callback);
        }

        /// <summary>
        /// 异步加载Bundle
        /// </summary>
        /// <param name="bundleName"></param>
        /// <param name="callback"></param>
        public void LoadBundleAsyn(string bundleName, System.Action<string> callback)
        {
            if (!bundleDic.ContainsKey(bundleName))
            {
                if (callback != null)
                    callback("not eixst");
                return;
            }
            AssetBundleObj assetStateObj = bundleDic[bundleName];
            LoadAssetBundleAsyn(bundleName, assetStateObj.bundelExistStatus, callback);
        }

        /// <summary>
        /// 异步
        /// </summary>
        /// <param name="bundleName"></param>
        /// <param name="bundelExistStatus"></param>
        /// <param name="callback"></param>
        private void LoadAssetBundleAsyn(string bundleName, AssetExistStatusEnum bundelExistStatus, System.Action<string> callback)
        {
            if (!bundleDic.ContainsKey(bundleName))
            {
                if (callback != null)
                    callback("not eixst");
                return;
            }
            AssetBundleObj assetStateObj = bundleDic[bundleName];
            if (assetStateObj.bundle != null)
            {
                if (callback != null)
                    callback(null);
                return;
            }
            string _err = "";

            ResLibaryTool.UTStartCoroutine(loadAssetBundle(assetStateObj.bName, bundelExistStatus, (error) =>
            {
                if (!string.IsNullOrEmpty(error))
                    _err = string.Format("{0}:{1}", _err, error);
                string[] alldes = assetStateObj.allDependencies.ToArray();
                if (alldes.Length > 0)
                {
                    int upind = 0;
                    for (int i = 0; i < alldes.Length; i++)
                    {
                        ResLibaryTool.UTStartCoroutine(loadAssetBundle(alldes[i], bundelExistStatus, (err) =>
                         {
                             upind++;
                             if (!string.IsNullOrEmpty(err))
                                 _err = string.Format("{0}:{1}", _err, err);
                             if (upind == assetStateObj.dependencies.Length)
                             {
                                 if (callback != null)
                                     callback(_err);
                             }
                         }));
                    }
                }
                else
                {
                    if (callback != null)
                        callback(_err);
                }
            }));
        }

        /// <summary>
        /// 同步
        /// </summary>
        /// <param name="bundleName"></param>
        /// <param name="bundelExistStatus"></param>
        private void LoadAssetBundleSync(string bundleName, AssetExistStatusEnum bundelExistStatus)
        {
            if (!bundleDic.ContainsKey(bundleName))
            {
                return;
            }
            AssetBundleObj bundleObj = bundleDic[bundleName];
            bool canLoad = true;
            string bundleUrl = bundleObj.bundlePath;
#if UNITY_ANDROID && !UNITY_EDITOR
            string resfile = bundleUrl.Replace("\\", "/");
            int index = resfile.IndexOf("StreamingAssets/", StringComparison.CurrentCultureIgnoreCase);              
            canLoad = index < 0;
#endif

            AssetBundle ab = bundleObj.bundle == null && canLoad ? AssetBundle.LoadFromFile(bundleObj.bundlePath) : bundleObj.bundle;
            if (ab != null)
            {
                bundleObj.bundle = ab;
            }

            string[] alldenps = bundleObj.allDependencies.ToArray();
            for (int i = 0; i < alldenps.Length; i++)
            {
                AssetBundleObj assetBundleObj = bundleDic[alldenps[i]];
#if UNITY_ANDROID && !UNITY_EDITOR
            resfile = assetBundleObj.bundlePath.Replace("\\", "/");
            index = resfile.IndexOf("StreamingAssets/", StringComparison.CurrentCultureIgnoreCase);              
            canLoad = index < 0;
#endif

                AssetBundle bundle = assetBundleObj.bundle == null && canLoad ? AssetBundle.LoadFromFile(assetBundleObj.bundlePath) : assetBundleObj.bundle;
                if (bundle != null)
                {
                    assetBundleObj.bundle = bundle;
                }

            }
        }

        public void UpdateAssetBundle(ABStructObj[] abs, AssetExistStatusEnum bundelExistStatus, System.Action<float> prograss, System.Action<string> callback)
        {
            List<string> waitLoadGAB = new List<string>();
            for (int i = 0; i < abs.Length; i++)
            {
                string bundleUrl = abs[i].bundleUrl;
                AssetBundleObj assetStateObj;
                string bname = abs[i].bName;
                if (!bundleDic.ContainsKey(bname))
                {
                    assetStateObj = new AssetBundleObj();
                    assetStateObj.bName = bname;
                    //assetStateObj.contentDic = new Dictionary<string, List<string>>();
                    assetStateObj.bundlePath = bundleUrl;
                    assetStateObj.dependencies = abs[i].dependencies;
                    assetStateObj.bundelExistStatus = bundelExistStatus;

                    bundleDic[bname] = assetStateObj;
                    for (int j = 0; j < abs[i].contents.Length; j++)
                    {
                        string assetName = abs[i].contents[j].assetName;
                        string assetType = abs[i].contents[j].assetType;
                        if (!resourceDict.ContainsKey(assetType))
                            resourceDict[assetType] = new Dictionary<string, ABContentAssetObj>();
                        if (!resourceDict[assetType].ContainsKey(assetName))
                        {
                            LibaryStateObj libaryObj = new LibaryStateObj();
                            libaryObj.m_Name = assetName;
                            libaryObj.m_Status = LibaryStatusEnum.DIR_ASSETBUNDER;
                            libaryObj.m_Type = assetType;
                            libaryObj.m_Path = bundleUrl;

                            //if (!assetStateObj.contentDic.ContainsKey(libaryObj.m_Type))
                            //    assetStateObj.contentDic[libaryObj.m_Type] = new List<string>();
                            //if (!assetStateObj.contentDic[libaryObj.m_Type].Contains(libaryObj.m_Name))
                            //    assetStateObj.contentDic[libaryObj.m_Type].Add(libaryObj.m_Name);
                            ABContentAssetObj aBAssetObj = new ABContentAssetObj();
                            aBAssetObj.assetName = libaryObj.m_Name;
                            aBAssetObj.assetType = libaryObj.m_Type;
                            aBAssetObj.bundelName = bname;
                            resourceDict[aBAssetObj.assetType][aBAssetObj.assetName] = aBAssetObj;
                            UpdateAssetCallback(libaryObj);
                        }
                    }
                }
                else
                    assetStateObj = bundleDic[bname];
                if (bundelExistStatus == AssetExistStatusEnum.Globle)
                {
                    if (!waitLoadGAB.Contains(bname))
                        waitLoadGAB.Add(bname);
                }
            }

            for (int i = 0; i < abs.Length; i++)
            {
                string bundleUrl = abs[i].bundleUrl;
                string bname = abs[i].bName;
                AssetBundleObj assetStateObj = bundleDic[bname];
                loadAllDependencies(assetStateObj, assetStateObj.dependencies);
            }
            int upind = 0;
            string err = "";
            string[] was = waitLoadGAB.ToArray();
            for (int i = 0; i < was.Length; i++)
            {
                string bname = was[i];
                if (bundleDic[bname].bundle == null)
                {
                    string bundleUrl = bundleDic[bname].bundlePath;
                    LoadAssetBundleAsyn(bname, AssetExistStatusEnum.Globle, (error) =>
                    {
                        upind++;
                        if (prograss != null)
                            prograss((float)upind / (float)was.Length);
                        if (!string.IsNullOrEmpty(error))
                        {
                            err = string.Format("{0}:{1}", err, error);
                        }
                        if (upind == was.Length)
                        {
                            if (callback != null)
                                callback(err);
                        }
                    });
                }
                else
                {
                    upind++;
                    if (prograss != null)
                        prograss((float)upind / (float)was.Length);
                    if (upind == was.Length)
                    {
                        if (callback != null)
                            callback(err);
                    }
                }
            }
            if (was.Length == 0)
            {
                if (prograss != null)
                    prograss(1);
                if (callback != null)
                    callback(err);
            }
        }

        private void loadAllDependencies(AssetBundleObj assetStateObj, string[] dependencies)
        {
            for (int i = 0; i < dependencies.Length; i++)
            {
                if (!bundleDic.ContainsKey(dependencies[i]) || assetStateObj.allDependencies.Contains(dependencies[i]) || assetStateObj.bName == dependencies[i])
                    continue;
                assetStateObj.allDependencies.Add(dependencies[i]);
                AssetBundleObj bundleObj = bundleDic[dependencies[i]];
                loadAllDependencies(assetStateObj, bundleObj.dependencies);
            }
        }
        private IEnumerator loadAssetBundle(string bname, AssetExistStatusEnum statusEnum, System.Action<string> callback)
        {
            string err = null;
            AssetBundleObj assetStateObj = bundleDic[bname];
            AssetBundle bundle = assetStateObj.bundle;
            if (assetStateObj.bundle == null)
            {
                string bundleUrl = assetStateObj.bundlePath;
#if UNITY_ANDROID && !UNITY_EDITOR
            string resfile = bundleUrl.Replace("\\", "/");
            int index = resfile.IndexOf("StreamingAssets/", StringComparison.CurrentCultureIgnoreCase);              
            if (index < 0)
            {
                AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(bundleUrl);
                if (request != null)
                {
                    while (!request.isDone)
                    {
                        yield return new WaitForFixedUpdate();
                    }
                    bundle = request.assetBundle;
                }
                else
                {
                    err = "failed";
                }
            }
            else
            {
                WWW www = new WWW(@"file://" + bundleUrl);
                while (!www.isDone)
                {
                    yield return new WaitForFixedUpdate();
                }
                if (string.IsNullOrEmpty(www.error) && www.assetBundle != null)
                {
                    bundle = www.assetBundle;
                }
                else
                {
                    err = "failed";
                }
                www.Dispose();
            }
#else
                AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(bundleUrl);
                if (request != null)
                {
                    while (!request.isDone)
                    {
                        yield return new WaitForFixedUpdate();
                    }
                    bundle = request.assetBundle;
                }
                else
                {
                    err = "failed";
                }
#endif
            }
            if (bundle != null)
            {
                assetStateObj.bundle = bundle;
            }
            if (callback != null)
                callback(err);
            yield return null;

        }
        string ILibaryHandle.GetTextAsset(string objName)
        {
            TextAsset textAsset = ((ILibaryHandle)this).GetObject<TextAsset>(objName);
            if (textAsset != null)
                return textAsset.text;
            return null;
        }
        GameObject GetGameObject(string objName)
        {
            return ((ILibaryHandle)this).GetObject<GameObject>(objName);
        }
        Texture2D ILibaryHandle.GetTexture2d(string objName)
        {
            return ((ILibaryHandle)this).GetObject<Texture2D>(objName);
        }

        RenderTexture GetRenderTexture(string objName)
        {
            return ((ILibaryHandle)this).GetObject<RenderTexture>(objName);
        }

        Sprite ILibaryHandle.GetSprite(string objName)
        {
            return ((ILibaryHandle)this).GetObject<Sprite>(objName);
        }

        Material GetMatiral(string objName)
        {
            return ((ILibaryHandle)this).GetObject<Material>(objName);
        }

        AudioClip GetAudioClip(string objName)
        {
            return ((ILibaryHandle)this).GetObject<AudioClip>(objName);
        }

        VideoClip GetVideoClip(string objName)
        {
            return ((ILibaryHandle)this).GetObject<VideoClip>(objName);
        }

        MovieTexture GetMovieTexture(string objName)
        {
            return ((ILibaryHandle)this).GetObject<MovieTexture>(objName);
        }

        T ILibaryHandle.GetObject<T>(string objName)
        {
            string _type = "";
            if (!ResLibaryConfig.TryGetTypeName<T>(out _type))
                return null;
            return (T)((ILibaryHandle)this).GetUnityObject(_type, objName);
        }

        UnityEngine.Object ILibaryHandle.GetUnityObject(string _type, string objName)
        {
            if (resourceDict.ContainsKey(_type))
            {
                if (resourceDict[_type].ContainsKey(objName))
                {
                    ABContentAssetObj stateObj = resourceDict[_type][objName];
                    string bname = stateObj.bundelName;
                    if (!bundleDic.ContainsKey(bname))
                        return null;
                    AssetBundleObj bundleObj = bundleDic[bname];
                    LoadAssetBundleSync(bundleObj.bName, bundleObj.bundelExistStatus);
                    if (bundleObj.bundle != null)
                    {
                        UnityEngine.Object t = bundleObj.bundle.LoadAsset(objName, ResLibaryConfig.ExistTypeNameToType[_type]);
                        if (t != null)
                        {
                            bundleObj.quoteDic[bundleObj.bundelExistStatus]++;
                            string[] alldenps = bundleObj.allDependencies.ToArray();
                            for (int i = 0; i < alldenps.Length; i++)
                            {
                                AssetBundleObj abo = bundleDic[alldenps[i]];
                                abo.quoteDic[bundleObj.bundelExistStatus]++;
                            }
                        }
                        return t;
                    }
                }
            }
            return null;
        }

        LibaryExistStatusEnum ILibaryHandle.TryGetObject(LibaryTypeEnum libaryTypeEnum, string objName, out object data)
        {
            data = ((ILibaryHandle)this).GetUnityObject(ResLibaryConfig.ExistType[libaryTypeEnum], objName);
            LibaryExistStatusEnum libaryExistStatusEnum = LibaryExistStatusEnum.LibaryExistStatus_UnityEngineObject;
            return libaryExistStatusEnum;
        }

        void ILibaryHandle.InsertLibrary(object data) { }

        void ILibaryHandle.DeleteLiibrary(string _type, string name)
        {


        }

        void ILibaryHandle.releaseObj(LibaryTypeEnum libaryTypeEnum, string objName)
        {
            string _type = ResLibaryConfig.ExistType[libaryTypeEnum];
            ((ILibaryHandle)this).releaseObj(_type, objName);
        }

        void ILibaryHandle.releaseObj(string _type, string objName)
        {
            if (!resourceDict.ContainsKey(_type))
                return;
            if (!resourceDict[_type].ContainsKey(objName))
                return;
            if (resourceDict.ContainsKey(_type))
            {
                ABContentAssetObj stateObj = resourceDict[_type][objName];
                string bname = stateObj.bundelName;
                AssetBundleObj bundleObj = bundleDic[bname];

                bundleObj.quoteDic[bundleObj.bundelExistStatus] = bundleObj.bundelExistStatus == AssetExistStatusEnum.Once ?
                        0 : Mathf.Max(0, bundleObj.quoteDic[bundleObj.bundelExistStatus] - 1);
                string[] alldenps = bundleObj.allDependencies.ToArray();
                for (int i = 0; i < alldenps.Length; i++)
                {
                    AssetBundleObj abo = bundleDic[alldenps[i]];
                    abo.quoteDic[bundleObj.bundelExistStatus] = bundleObj.bundelExistStatus == AssetExistStatusEnum.Once ?
                        0 : Mathf.Max(0, abo.quoteDic[bundleObj.bundelExistStatus] - 1);

                    if (abo.bundelExistStatus == AssetExistStatusEnum.Quote)
                    {
                        if (abo.quoteDic[AssetExistStatusEnum.Quote] <= 0 &&
                            abo.quoteDic[AssetExistStatusEnum.Scene] <= 0 &&
                            abo.quoteDic[AssetExistStatusEnum.Globle] <= 0 &&
                            abo.quoteDic[AssetExistStatusEnum.Once] <= 0)
                        {
                            abo.bundle.Unload(true);
                            abo.bundle = null;
                        }
                    }
                }

                if (bundleObj.bundelExistStatus == AssetExistStatusEnum.Quote)
                {
                    if (bundleObj.quoteDic[AssetExistStatusEnum.Quote] <= 0 &&
                        bundleObj.quoteDic[AssetExistStatusEnum.Scene] <= 0 &&
                        bundleObj.quoteDic[AssetExistStatusEnum.Globle] <= 0 &&
                        bundleObj.quoteDic[AssetExistStatusEnum.Once] <= 0)
                    {
                        bundleObj.bundle.Unload(true);
                        bundleObj.bundle = null;
                    }
                }
            }
        }

        void ILibaryHandle.releaseObj(UnityEngine.Object obj)
        {
            string _type = obj.GetType().Name;
            ((ILibaryHandle)this).releaseObj(_type, obj.name);
        }

        void ILibaryHandle.releaseAll()
        {
            foreach (var bundle in bundleDic.Values)
            {
                if (bundle.bundle != null)
                    bundle.bundle.Unload(true);
            }
            bundleDic.Clear();
            resourceDict.Clear();

#if UNITY_EDITOR
                if (!ResLibaryConfig.isPlaying)
                {
            edit_singleton = null;
                 }
            else{
            play_singleton = null;
            }
#else
            play_singleton = null;
#endif
        }

        void ILibaryHandle.releaseScene()
        {
            foreach (var bundleObj in bundleDic.Values)
            {
                if (bundleObj.bundelExistStatus == AssetExistStatusEnum.Scene)
                {
                    bundleObj.quoteDic[bundleObj.bundelExistStatus] = 0;
                    string[] alldenps = bundleObj.allDependencies.ToArray();
                    for (int i = 0; i < alldenps.Length; i++)
                    {
                        AssetBundleObj abo = bundleDic[alldenps[i]];
                        abo.quoteDic[bundleObj.bundelExistStatus] = 0;
                        if (abo.quoteDic[AssetExistStatusEnum.Quote] <= 0 &&
                              abo.quoteDic[AssetExistStatusEnum.Scene] <= 0 &&
                              abo.quoteDic[AssetExistStatusEnum.Globle] <= 0 &&
                              abo.quoteDic[AssetExistStatusEnum.Once] <= 0)
                        {
                            abo.bundle.Unload(true);
                            abo.bundle = null;
                        }
                    }

                    if (bundleObj.quoteDic[AssetExistStatusEnum.Quote] <= 0 &&
                            bundleObj.quoteDic[AssetExistStatusEnum.Scene] <= 0 &&
                            bundleObj.quoteDic[AssetExistStatusEnum.Globle] <= 0 &&
                            bundleObj.quoteDic[AssetExistStatusEnum.Once] <= 0)
                    {
                        bundleObj.bundle.Unload(true);
                        bundleObj.bundle = null;
                    }
                }
            }
        }
    }
}
